#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>

#include <cuse_lowlevel.h> // cuse
#include <errno.h>         // error numbers
#include <string.h>        // memset
#include <unistd.h>        // chmod

#include <ftdi.hpp>        // ftdi

#include <sys/ioctl.h>
#include <linux/ppdev.h>   // parport ioctl commands definitions

#include <unistd.h>

using namespace std;

//============================================================================//

typedef struct {
	unsigned char b0 : 1;
	unsigned char b1 : 1;
	unsigned char b2 : 1;
	unsigned char b3 : 1;
	unsigned char b4 : 1;
	unsigned char b5 : 1;
	unsigned char b6 : 1;
	unsigned char b7 : 1;
} bitfield;

typedef union {
	unsigned char byte;
	bitfield      bit;
} t_data;
typedef union {
	unsigned char byte;
	bitfield      bit;
	struct {
		unsigned char          : 3;
		unsigned char error    : 1;
		unsigned char select   : 1;
		unsigned char paperout : 1;
		unsigned char ack      : 1;
		unsigned char busy     : 1;
	} pin;
} t_status;
typedef union {
	unsigned char byte;
	bitfield      bit;
	struct {
		unsigned char strobe   : 1;
		unsigned char linefeed : 1;
		unsigned char reset    : 1;
		unsigned char select   : 1;
		unsigned char          : 4;
	} pin;
} t_control;

//============================================================================//

static const char name[] = "parport1";
static fstream log("/dev/null", ios::app | ios::in | ios::out);

t_data    data;
t_status  status;
t_control control; // not used right now

t_data    connector;

Ftdi::Context ftdi;

// data buffer, just trying to speed things up a little bit
vector<unsigned char> buffer;

//============================================================================//

static void fakepp_open(fuse_req_t req, struct fuse_file_info *fi)
{
	//fuse_reply_err(req, ENODEV);
	//fuse_reply_open(req, fi);
	
	ftdi.set_interface(INTERFACE_B);
	
	if(ftdi.open(0x0403, 0x6010) < -1){
		fuse_reply_err(req, ENODEV);
		return;
	}
	
	ftdi.set_bitmode(0x1F, BITMODE_BITBANG);
	ftdi.set_baud_rate(150000);
	ftdi.set_latency(1);
	ftdi.set_write_chunk_size(buffer.capacity()*2);
	
	fuse_reply_open(req, fi);
}

static void fakepp_release(fuse_req_t req, struct fuse_file_info *fi){
	//fuse_reply_err(req, 0);
	
	ftdi.close();
	fuse_reply_err(req, 0);
}

static void fakepp_read(fuse_req_t req,
                        size_t size, off_t off,
                        struct fuse_file_info *fi)
{
	//fuse_reply_err(req, ENODATA);
	//fuse_reply_buf(req, buff, size);
	
	fuse_reply_buf(req, NULL, 0);
}

static void fakepp_write(fuse_req_t req,
                         const char *buf, size_t size, off_t off,
                         struct fuse_file_info *fi)
{
	//fuse_reply_err(req, EBUSY);
	//fuse_reply_write(req, written_bytes);
	
	fuse_reply_write(req, 0);
}

#define current_ioctl() req, arg, in_buf, in_bufsz, out_bufsz
template <class T>
bool ioctl_get(fuse_req_t req, void *arg,
               const void *in_buf, size_t in_bufsz, size_t out_bufsz, T &x){
	if(!in_bufsz){
		struct iovec iov = { arg, sizeof(T) };
		fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
		return false;
	}else{
		x = *(const T *)in_buf;
		return true;
	}
}
template <class T>
bool ioctl_set(fuse_req_t req, void *arg,
               const void *in_buf, size_t in_bufsz, size_t out_bufsz, T &x){
	if(!out_bufsz){
		struct iovec iov = { arg, sizeof(T) };
		fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
		return false;
	}else{
		//fuse_reply_ioctl(req, 0, &x, sizeof(T));
		return true;
	}
}

ostream &operator<<(ostream &o, bitfield &b){
	return o << (int)b.b7 << (int)b.b6 << (int)b.b5 << (int)b.b4 << " "
	         << (int)b.b3 << (int)b.b2 << (int)b.b1 << (int)b.b0;
}

static void fakepp_ioctl(fuse_req_t req,
                   int cmd, void *arg,
                   struct fuse_file_info *fi, unsigned flags,
                   const void *in_buf, size_t in_bufsz, size_t out_bufsz)
{
	//fuse_reply_ioctl(req, result, buff, size);
	//ioctl_set(current_ioctl(), data);
	//ioctl_get(current_ioctl(), data) || return;
	
	/* unclaimed ioctl */
	switch(cmd){
		case PPCLAIM: case PPEXCL:
			// assume everything is allright
			fuse_reply_ioctl(req,0,NULL,0);
			return;
		case PPSETMODE:  case PPGETMODE:  case PPSETPHASE:
		case PPGETPHASE: case PPGETMODES: case PPSETFLAGS:
		case PPGETFLAGS:
			fuse_reply_ioctl(req,0,NULL,0);
			return;
	}
	
	/* claimed ioctl */
	switch(cmd){		
		case PPRSTATUS:
			// prepare the ioctl buffer (for output)
			if(!ioctl_set(current_ioctl(), status.byte))
				return;
			
			// flush the writing buffer
			if(buffer.size()){
				ftdi.write(&buffer[0], buffer.size());
				buffer.clear();
			}
			
			// get the connector
			ftdi.read_pins((unsigned char *)&connector.byte);
			
			// build the status
			status.pin.ack    = data.bit.b4;
			status.pin.error  = 0;
			status.pin.busy   = !connector.bit.b6;
			status.pin.select = connector.bit.b7;
			
			log << name << ":                    < " << status.bit << endl;
			
			// write the output buffer
			fuse_reply_ioctl(req, 0, &status.byte, sizeof(status.byte));
			return;
			
		case PPWDATA:
			// prepare the ioctl buffer (for input) and read it
			if(!ioctl_get(current_ioctl(), data.byte))
				return;
			
			// build the connector
			connector.bit.b0 = data.bit.b0;
			connector.bit.b1 = data.bit.b1;
			connector.bit.b2 = data.bit.b2;
			connector.bit.b3 = data.bit.b3;
			connector.bit.b4 = data.bit.b6;
			
			log << name << ": > " << data.bit << endl;
			
			// output the connector
			buffer.push_back(connector.byte);
			if(buffer.size() == buffer.capacity()){
				ftdi.write(&buffer[0], buffer.size());
				buffer.clear();
			}
			
			// reply success
			fuse_reply_ioctl(req,0,NULL,0);
			return;
			
		case PPRDATA:   case PPRCONTROL: case PPYIELD:
		case PPRELEASE: case PPWCONTROL: case PPFCONTROL:
		case PPDATADIR: case PPNEGOT:    case PPWCTLONIRQ:
		case PPCLRIRQ:  case PPSETTIME:  case PPGETTIME:
			fuse_reply_ioctl(req,0,NULL,0);
			return;
	}
	
	log << name << ": ioctl (invalid) called" << endl;
	fuse_reply_ioctl(req,EINVAL,NULL,0);
}

static void fakepp_poll(fuse_req_t req, struct fuse_file_info *fi,
                 struct fuse_pollhandle *ph)
{
	//fuse_reply_poll(req, mask);
	log << name << ": poll called" << endl;
	fuse_reply_poll(req, 0);
}

//============================================================================//

static void fakepp_init_done(void *userdata)
{
	char dev_name[128] = "/dev/";
	strncat(dev_name, name, sizeof(dev_name) - 6);
	chmod(dev_name, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
	
	// reserve 2048 bytes in the write buffer
	buffer.reserve(512);
}

//============================================================================//

static struct cuse_lowlevel_ops operations;

static int cuse_init(int argc, char *argv[],
                     const char *device_name, unsigned major, unsigned minor)
{
	char dev_name[128] = "DEVNAME=";
	const char *dev_info_argv[] = { dev_name };
	struct cuse_info info;
	
	strncat(dev_name, device_name, sizeof(dev_name) - 9);
	
	memset(&info, 0, sizeof(info));
	info.dev_major     = major;
	info.dev_minor     = minor;
	info.dev_info_argc = 1;
	info.dev_info_argv = dev_info_argv;
	info.flags         = CUSE_UNRESTRICTED_IOCTL;
	
	return cuse_lowlevel_main(argc, argv, &info, &operations, NULL);
}

int main(int argc, char **argv)
{
	operations.open      = fakepp_open;
	operations.release   = fakepp_release;
	operations.read      = fakepp_read;
	operations.write     = fakepp_write;
	operations.ioctl     = fakepp_ioctl;
	operations.poll      = fakepp_poll;
	operations.init_done = fakepp_init_done;
	
	return cuse_init(argc, argv, name, 0, 0);
}

